<template>
  <view
    :class="floatingBubbleClass"
    :style="floatingBubbleStyle"
    @touchstart="onTouchStart"
    @touchmove.stop.prevent="onTouchMove"
    @touchend="onTouchEnd"
    @touchcancel="onTouchEnd"
    @mousedown="onMouseDown"
    @click="onClick"
  >
    <slot></slot>
  </view>
</template>

<script>
import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from "vue";
import { computed, getCurrentInstance, onMounted, ref, watch } from "vue";
import {
  classNames,
  stringifyStyle,
  createBem,
  getBoundingClientRect,
  uniqid,
  getWindowInfo,
  clamp
} from "../../utils";
import {
  defaultFloatingBubbleProps
} from "./common";
import { useMouseDown, useTimeout } from "../../use";
/**
 * @property {string} rootClass 组件根元素类名，默认值：-。
 * @property {StyleValue} rootStyle 组件根元素样式，默认值：-。
 * @property {'x' | 'y' | 'both' | 'none'} axis 允许拖拽的方向轴，默认值：'y'。
 * @property {'x' | 'y'} magnet 吸附到指定轴最近的一边，默认值：-。
 * @property {number} gapX 气泡与窗口左右两边的最小间距，单位为 px，默认值：24。
 * @property {number} gapY 气泡与窗口上下两边的最小间距，单位为 px，默认值：24。
 * @property {{ x: number; y: number }} offset 控制气泡的位置，默认值：-。
 * @event {(event: any) => void} click 点击时触发
 * @event {(offset: { x: number; y: number }) => void} update 因用户拖拽导致位置改变时触发
 */
export default  _defineComponent({
  ...{
    options: {
      virtualHost: true,
      styleIsolation: "shared"
    }
  },
  __name: "floating-bubble",
  props:  _mergeDefaults({
    rootStyle: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
    rootClass: { type: String, required: false },
    axis: { type: String, required: false },
    magnet: { type: String, required: false },
    gapX: { type: Number, required: false },
    gapY: { type: Number, required: false },
    offset: { type: Object, required: false }
  }, defaultFloatingBubbleProps),
  emits: ["click", "update:offset"],
  setup(__props, { expose: __expose, emit: __emit }) {
    __expose();
    const props = __props;
    const emit = __emit;
    const bem = createBem("floating-bubble");
    const instance = getCurrentInstance();
    const bubbleId = uniqid();
    let bubbleRect;
    const { windowWidth, windowHeight } = getWindowInfo();
    let downCoord = {
      x: 0,
      y: 0
    };
    const initialized = ref(false);
    const position = ref({
      x: 0,
      y: 0
    });
    watch(
      () => props.offset,
      () => {
        if (props.offset) {
          position.value = props.offset;
        }
      }
    );
    const animated = ref(false);
    const { start: nonAnimatedLater, stop: cancelNonAnimated } = useTimeout(() => {
      animated.value = false;
    }, 500);
    function getMinX() {
      return props.gapX;
    }
    function getMaxX() {
      return windowWidth - props.gapX - bubbleRect.width;
    }
    function getMinY() {
      return props.gapY + 44 + 25;
    }
    function getMaxY() {
      return windowHeight - props.gapY - bubbleRect.height;
    }
    onMounted(async () => {
      bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
      position.value = props.offset ?? {
        x: getMaxX(),
        y: getMaxY()
      };
      bubbleRect = void 0;
      initialized.value = true;
    });
    const onTouchStart = async (event) => {
      cancelNonAnimated();
      animated.value = false;
      downCoord = {
        x: event.touches[0].clientX,
        y: event.touches[0].clientY
      };
      bubbleRect = await getBoundingClientRect(`.${bubbleId}`, instance);
    };
    const onTouchMove = (event) => {
      if (!bubbleRect) {
        return;
      }
      let x = 0;
      let y = 0;
      if (props.axis === "none") {
        x = getMaxX();
        y = getMaxY();
      } else {
        const deltaX = event.touches[0].clientX - downCoord.x;
        const deltaY = event.touches[0].clientY - downCoord.y;
        x = bubbleRect.left + deltaX;
        y = bubbleRect.top + deltaY;
        x = clamp(x, getMinX(), getMaxX());
        y = clamp(y, getMinY(), getMaxY());
        if (props.axis === "y") {
          x = getMaxX();
        } else if (props.axis === "x") {
          y = getMaxY();
        }
      }
      const offset = {
        x,
        y
      };
      position.value = offset;
      emit("update:offset", offset);
    };
    const onTouchEnd = () => {
      if (bubbleRect) {
        if (props.magnet) {
          let { x, y } = position.value;
          if (props.magnet === "x") {
            x = x < (windowWidth - bubbleRect.width) / 2 ? getMinX() : getMaxX();
          } else if (props.magnet === "y") {
            y = y < (windowHeight - bubbleRect.height) / 2 ? getMinY() : getMaxY();
          }
          const offset = {
            x,
            y
          };
          position.value = offset;
          emit("update:offset", offset);
        }
      }
      animated.value = true;
      nonAnimatedLater();
      bubbleRect = void 0;
    };
    const onMouseDown = useMouseDown(onTouchStart, onTouchMove, onTouchEnd);
    const onClick = (event) => {
      emit("click", event);
    };
    const floatingBubbleClass = computed(() => {
      return classNames(
        bem.b(),
        bem.m("animated", animated.value),
        bem.m("initialized", initialized.value),
        props.rootClass,
        bubbleId
      );
    });
    const floatingBubbleStyle = computed(() => {
      return stringifyStyle(props.rootStyle, {
        transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
      });
    });
    const __returned__ = { props, emit, bem, instance, bubbleId, get bubbleRect() {
      return bubbleRect;
    }, set bubbleRect(v) {
      bubbleRect = v;
    }, windowWidth, windowHeight, get downCoord() {
      return downCoord;
    }, set downCoord(v) {
      downCoord = v;
    }, initialized, position, animated, nonAnimatedLater, cancelNonAnimated, getMinX, getMaxX, getMinY, getMaxY, onTouchStart, onTouchMove, onTouchEnd, onMouseDown, onClick, floatingBubbleClass, floatingBubbleStyle };
    return __returned__;
  }
});
</script>

<style lang="scss">
@import './index.scss';
</style>